import {
  addCls,
  createGuideEls,
  getTipStyle,
  GUIDE_EVENT_DISABLE,
  GUIDE_EVENT_ENABLE,
  removeCls,
  setCls,
  setStyle,
  showEl,
} from "./dom";
import { debounce, firstUpCase, mergeObj } from "./util";

interface KeyMap {
  /** 下一步 */
  next?: number | number[];
  /** 上一步 */
  prev?: number | number[];
  /** 跳过 */
  skip?: number | number[];
  /** 完成 */
  complete?: number | number[];
}

interface Props {
  /** 区域是否可以点击 */
  targetClickEnable?: boolean;
  /** 点击区域跳到下一步 */
  clickTargetNext?: boolean;
  /** 滚动到目标区域 */
  scrollToTarget?: boolean;
  /** 显示提示框 默认true */
  showTooltip?: boolean;
  /** 显示操作按钮 默认true */
  showOperation?: boolean;
  /** 显示跳过 默认true */
  showSkip?: boolean;
  /** 显示下一步 默认true */
  showNextBtn?: boolean;
  /** 显示上一步 默认true */
  showPrevBtn?: boolean;
  /** 显示完成 默认true */
  showCompleteBtn?: boolean;
  /** 下一步文案 */
  nextText?: string;
  /** 上一步文案 */
  prevText?: string;
  /** 跳过文案 */
  skipText?: string;
  /** 完成文案 */
  completeText?: string;
  /** 自动跳转到下一步 */
  autoNext?: boolean;
  /** 自动跳转时间 单位秒 默认5秒 */
  autoNextTime?: number;
  /** 提示框方向，默认自动 */
  position?: "left" | "top" | "right" | "bottom";
}

interface Events {
  /** 初始化完成 */
  onInit?: () => void;
  /** 开始 */
  onStart?: () => void;
  /** 跳过 */
  onSkip?: () => void;
  /** 完成 */
  onComplete?: () => void;
  /** 退出 跳过和完成都会触法 */
  onExit?: () => void;
  /** 进入每一步之后 */
  afterEnter?: (step?: Step) => void;
  /** 开始之前 */
  beforeStart?: (next: Function) => void;
  /** 执行当前步骤之前 */
  beforeEnter?: (next: Function) => void;
  /** 下一步之前 */
  beforeNext?: (next: Function, to: Step, from: Step) => void;
  /** 上一步之前 */
  beforePrev?: (next: Function, to: Step, from: Step) => void;
  /** 跳过之前 */
  beforeSkip?: (next: Function, step: Step) => void;
  /** 完成之前 */
  beforeComplete?: (next: Function, step: Step) => void;
}

interface Step extends Props {
  /** 元素id 或者元素本身 或函数返回元素 */
  id?: string | HTMLElement | (() => HTMLElement | null);
  /** 提示文案，支持html字符串 */
  tip?: string;
  beforeEnter?: (next: Function) => void;
  beforeNext?: (next: Function, to: Step, from: Step) => void;
  beforePrev?: (next: Function, to: Step, from: Step) => void;
  afterEnter?: (step: Step) => void;
}

type GlobalHookName =
  | "onInit"
  | "onStart"
  | "onSkip"
  | "onComplete"
  | "onExit"
  | "afterEnter"
  | "beforeStart"
  | "beforeEnter"
  | "beforeNext"
  | "beforePrev"
  | "beforeSkip"
  | "beforeComplete";
type StepHookName = "beforeEnter" | "afterEnter" | "beforeNext" | "beforePrev";
type HookParams = [any?, any?, any?];

export interface Config {
  /** 标识 */
  flag: string;
  /** 主题 */
  theme?: undefined | "default" | "dark";
  /** 键盘快捷键 当前按钮显示才会执行 */
  keyMap?: KeyMap;
  /** 属性 */
  props?: Props;
  /** 实践 */
  events?: Events;
  /** 步骤数据 */
  steps: Step[];
}

const defaultConfig: Config = {
  flag: "data-flag",
  theme: "default",
  keyMap: {
    // right enter
    next: [39, 13],
    // left
    prev: 37,
    // esc
    skip: 27,
    // enter right
    complete: [39, 13],
  },
  // 即使没有也需要设为undefined, 方便取key
  props: {
    targetClickEnable: false,
    clickTargetNext: false,
    scrollToTarget: true,
    showTooltip: true,
    showOperation: true,
    showSkip: true,
    showNextBtn: true,
    showPrevBtn: true,
    showCompleteBtn: true,
    nextText: "下一步",
    prevText: "上一步",
    skipText: "跳过",
    completeText: "完成",
    autoNext: true,
    autoNextTime: 5,
    position: undefined,
  },
  steps: [],
};

class Guide {
  private config: Config;
  /** 步骤index */
  private stepIndex: number = -1;
  /** 当前步骤 */
  private currentStep: Step | undefined;
  /** 是否执行中 */
  private execing: boolean = false;
  /** guide的所有元素 */
  private els: Record<string, HTMLElement> = {};
  /** 自动下一步定时器 */
  private autoNextTimer: any;
  /** 自动下一步倒计时 */
  private currentAutoNextTime: number = 0;
  /** 点击目标下一步事件 */
  private onClickTargetNext;
  /** 屏幕尺寸变化事件 */
  private onResize;
  /** 键盘事件 */
  private onKeyup;
  /** 当前步骤的props */
  private currentProps: Props | undefined;

  constructor(config: Config) {
    this.config = mergeObj(defaultConfig, config, true) as Config;
    this.hook("onInit");
    this.onResize = this.exec.bind(this);
    this.onKeyup = this.onKeyMap.bind(this);
    this.onClickTargetNext = this.next.bind(this);
  }
  /** 重置引导 */
  private resetCurrentStep() {
    this.stepIndex = -1;
    this.currentStep = undefined;
  }
  /**
   * 开始执行指引
   */
  start() {
    if (this.execing) {
      return;
    }
    this.resetCurrentStep();
    this.addWindowEvent();
    this.hook("beforeStart", () => {
      this.onStart();
    });
  }
  /** 当开始执行时 */
  private onStart() {
    this.execing = true;
    this.hook("onStart");
    const nextIndex = this.getNextStepIndex();
    this.toStep(this.getStepByIndex(nextIndex));
    addCls(document.body, GUIDE_EVENT_DISABLE);
  }
  /** 钩子统一处理 */
  private hook(
    hookName: StepHookName | GlobalHookName,
    callback?: Function | null,
    ...args: HookParams
  ) {
    const stepHook =
      this.currentStep && this.currentStep[hookName as StepHookName];
    const globalHook =
      this.config.events && this.config.events[hookName as GlobalHookName];
    let callbacktemp = callback;
    if (callback && globalHook && stepHook) {
      callbacktemp = () => {
        const params = [callback, ...args] as unknown as HookParams;
        globalHook(...params);
      };
    }
    if (callback) {
      args.unshift(callbacktemp);
    }
    if (stepHook) {
      stepHook(...args);
    } else if (globalHook) {
      globalHook(...args);
    } else if (callback) {
      callback();
    }
  }
  /** 根据index获取step */
  private getStepByIndex(index: number) {
    return this.config.steps[index];
  }
  /** 根据step获取index */
  private getIndexByStep(step: Step) {
    return this.config.steps.findIndex((v) => v === step);
  }
  /** 获取上一步index */
  private getPrevStepIndex() {
    let index = this.stepIndex;
    if (index === 0) {
      return -1;
    } else {
      return index - 1;
    }
  }
  /** 获取下一步index */
  private getNextStepIndex() {
    let index = this.stepIndex;
    if (index === -1) {
      index = 0;
    } else {
      index++;
    }
    if (index >= this.config.steps.length) {
      return -1;
    }
    return index;
  }
  /** 是否是第一步 */
  private isFirst() {
    return this.stepIndex === 0;
  }
  /** 是否是最后一步 */
  private isLast() {
    return this.stepIndex === this.config.steps.length - 1;
  }
  /** 是否显示跳过 */
  private isShowSkip() {
    return !this.isLast() && !!this.getProps().showSkip;
  }
  /** 是否显示上一步 */
  private isShowPrev() {
    return !this.isFirst() && !!this.getProps().showPrevBtn;
  }
  /** 是否显示下一步 */
  private isShowNext() {
    return !this.isLast() && !!this.getProps().showNextBtn;
  }
  /** 是否显示完成 */
  private isShowComplete() {
    return this.isLast() && !!this.getProps().showCompleteBtn;
  }
  /**
   * 上一步
   */
  prev() {
    const prevStep = this.getStepByIndex(this.getPrevStepIndex());
    if (prevStep) {
      this.hook(
        "beforePrev",
        () => {
          this.toStep(prevStep);
        },
        prevStep,
        this.currentStep
      );
    }
  }
  /**
   * 下一步
   */
  next() {
    const nextStep = this.getStepByIndex(this.getNextStepIndex());
    if (nextStep) {
      this.hook(
        "beforeNext",
        () => {
          this.toStep(nextStep);
        },
        nextStep,
        this.currentStep
      );
    } else if (this.isLast()) {
      this.complete();
    }
  }
  /** 自动下一步 */
  private autoNext() {
    let { autoNext, autoNextTime = 5 } = this.getProps();
    if (autoNext && !this.isLast()) {
      this.currentAutoNextTime = autoNextTime;
      this.autoNextTimer = setInterval(() => {
        this.currentAutoNextTime--;
        this.renderNextBtn();
        if (this.currentAutoNextTime <= 0) {
          this.clearAutoTimer();
          this.next();
        }
      }, 1000);
    }
  }
  /** 清除自动下一步定时器 */
  private clearAutoTimer() {
    if (this.autoNextTimer) {
      clearInterval(this.autoNextTimer);
      this.autoNextTimer = null;
    }
  }
  /**
   * 跳过指引
   */
  skip() {
    this.hook(
      "beforeSkip",
      () => {
        this.onSkip();
      },
      this.currentStep
    );
  }
  /** 当跳过指引 */
  private onSkip() {
    this.hook("onSkip");
    this.exit();
  }
  /**
   * 完成指引
   */
  complete() {
    if (!this.isLast()) {
      return;
    }
    this.hook(
      "beforeComplete",
      () => {
        this.onComplete();
      },
      this.currentStep
    );
  }
  /** 当完成指引 */
  private onComplete() {
    this.hook("onComplete");
    this.exit();
  }
  /**
   * 退出指引
   */
  exit() {
    this.clearAutoTimer();
    this.removeWindowEvent();
    showEl(this.els.wrap, false);
    removeCls(document.body, GUIDE_EVENT_DISABLE);
    this.els.wrap && document.body.removeChild(this.els.wrap);
    this.els = {};
    this.execing = false;
    this.hook("onExit");
  }
  /** 获取当前的目标元素 */
  private getCurrentEl() {
    if (typeof this.currentStep?.id === "function") {
      return this.currentStep.id();
    } else if (this.currentStep?.id instanceof HTMLElement) {
      return this.currentStep.id;
    } else if (this.currentStep?.id) {
      return document.querySelector(
        `[${this.config.flag}="${this.currentStep?.id}"]`
      ) as HTMLElement;
    }
    return null;
  }
  /** 执行某一步 */
  private toStep(step: Step) {
    if (!step || !this.execing) {
      return;
    }
    if (this.currentStep) {
      const lastEl = this.getCurrentEl();
      lastEl && this.removeElStatus(lastEl);
    }
    this.currentStep = step;
    this.stepIndex = this.getIndexByStep(this.currentStep!);
    this.currentProps = undefined;
    this.hook(
      "beforeEnter",
      () => {
        this.onAfterEnter();
      },
      this.currentStep
    );
  }
  /** 当进入某一步完成 */
  private onAfterEnter() {
    this.hook("afterEnter", null, this.currentStep);
    this.exec();
  }
  /** 执行某一步 */
  private exec() {
    this.clearAutoTimer();
    const el = this.getCurrentEl();
    const { scrollToTarget } = this.getProps();
    if (el && scrollToTarget) {
      el.scrollIntoView({ block: "center", inline: "center" });
    }
    this.autoNext();
    this.render(el);
  }
  /** 渲染指引 */
  private render(el?: HTMLElement | null) {
    if (!this.els.wrap) {
      const els = createGuideEls();
      this.els = els;
      document.body.appendChild(els.wrap);
      if (this.config.theme && this.config.theme !== "default") {
        addCls(els.wrap, this.config.theme!);
      }
      this.renderTheme(this.config.theme);
      this.addDefaultEvent();
    } else {
      showEl(this.els.wrap, true);
    }
    if (!el) {
      this.renderNoEl();
      return;
    }
    this.renderTooltip(el);
    this.renderBlock(el);
    this.addElStatus(el);
  }
  private renderTheme(theme = 'default') {
    if (this.config.theme) {
      removeCls(this.els.wrap, this.config.theme);
      addCls(this.els.wrap, theme);
    }
  }
  /** 渲染无目标元素的情况 */
  private renderNoEl() {
    const { block, tooltip, tip, arrow } = this.els!;
    setStyle(tooltip, {
      left: "50%",
      top: "50%",
      bottom: "initial",
      right: "initial",
      transform: "translate(-50%, -50%)",
    });
    setStyle(block, {
      width: 0,
      height: 0,
      left: "50%",
      top: "50%",
    });
    addCls(block, "guide-block-no-el");
    showEl(block, true);
    showEl(arrow, false);
    this.renderTooltipContent("1");
    this.renderBtns();
  }
  /** 渲染提示框 */
  private renderTooltip(el: HTMLElement) {
    const { tooltip, arrow } = this.els!;
    const { position } = this.getProps();
    const { mainPosition, arrowPosition, style } = getTipStyle(el, position);
    setStyle(tooltip, style);
    setCls(arrow, "guide-tooltip-arrow");
    addCls(arrow, mainPosition);
    addCls(arrow, arrowPosition);
    this.renderTooltipContent();
    this.renderBtns();
  }
  /** 渲染提示框内容 */
  private renderTooltipContent(opacity: "0" | "1" = "0") {
    const { tooltip, tip } = this.els!;
    tip.innerHTML = this.currentStep?.tip!;
    tooltip.style.opacity = opacity;
    showEl(tooltip, !!this.getProps().showTooltip);
  }
  /** 渲染操作按钮 */
  private renderBtns() {
    const { btns, skip, prev, next, complete } = this.els!;
    showEl(btns, !!this.getProps().showOperation);
    showEl(skip, this.isShowSkip());
    showEl(prev, this.isShowPrev());
    showEl(next, this.isShowNext());
    showEl(complete, this.isShowComplete());
    this.renderSkipBtn();
    this.renderPrevBtn();
    this.renderNextBtn();
    this.renderCompleteBtn();
  }
  /** 渲染上一步 */
  private renderPrevBtn() {
    const { prev } = this.els!;
    const { prevText } = this.getProps();
    prev.innerText = prevText!;
  }
  /** 渲染下一步 */
  private renderNextBtn() {
    const { next } = this.els!;
    const { nextText, autoNext } = this.getProps();
    next.innerText =
      nextText + (autoNext ? "(" + this.currentAutoNextTime + "秒)" : "");
  }
  /** 渲染跳过 */
  private renderSkipBtn() {
    const { skip } = this.els!;
    const { skipText } = this.getProps();
    skip.innerText = skipText!;
  }
  /** 渲染完成 */
  private renderCompleteBtn() {
    const { complete } = this.els!;
    const { completeText } = this.getProps();
    complete.innerText = completeText!;
  }
  /** 渲染目标区域框 */
  private renderBlock(el: HTMLElement) {
    const { block, tooltip } = this.els!;
    const old = block.getBoundingClientRect();
    const { width, height, left, top } = el.getBoundingClientRect();
    setStyle(block, {
      width: width + "px",
      height: height + "px",
      top: top + "px",
      left: left + "px",
    });
    if (
      old.width === width &&
      old.height === height &&
      old.left === left &&
      old.top === top
    ) {
      tooltip.style.opacity = "1";
    }
    removeCls(block, "guide-block-no-el");
    // 如果不可点击，则给block添加event属性
    if (!this.getProps().targetClickEnable) {
      addCls(block, GUIDE_EVENT_ENABLE);
    } else {
      removeCls(block, GUIDE_EVENT_ENABLE);
    }
  }
  /** 获取当前步骤的props */
  private getProps() {
    if (this.currentProps) {
      return this.currentProps;
    }
    const { props = {} } = this.config;
    const result: Props = {};
    for (const key in defaultConfig.props) {
      result[key] =
        this.currentStep && this.currentStep[key] !== undefined
          ? this.currentStep[key]
          : props[key] || undefined;
    }
    this.currentProps = result;
    Promise.resolve().then(() => {
      this.currentProps = undefined;
    });
    return result;
  }
  /** 添加元素状态 */
  private addElStatus(el: HTMLElement) {
    const { targetClickEnable, clickTargetNext } = this.getProps();
    if (targetClickEnable) {
      addCls(el, GUIDE_EVENT_ENABLE);
    }
    if (clickTargetNext) {
      el.addEventListener("click", this.onClickTargetNext, { once: true });
    }
  }
  /** 删除元素状态 */
  private removeElStatus(el: HTMLElement) {
    removeCls(el, GUIDE_EVENT_ENABLE);
    el.removeEventListener("click", this.onClickTargetNext);
  }
  /** 添加默认处理事件 */
  private addDefaultEvent() {
    const { block, tooltip, skip, next, prev, complete } = this.els!;
    block.addEventListener(
      "transitionend",
      (e) => {
        if (e.target === block) {
          this.showTooltip(tooltip);
        }
      },
      { capture: false }
    );
    next.addEventListener("click", this.next.bind(this));
    prev.addEventListener("click", this.prev.bind(this));
    skip.addEventListener("click", this.skip.bind(this));
    complete.addEventListener("click", this.complete.bind(this));
  }
  /** 显示提示框 */
  private showTooltip = debounce(function (tooltip: HTMLElement) {
    tooltip.style.opacity = "1";
  }, 20);
  /** 添加window事件 */
  private addWindowEvent() {
    window.addEventListener("resize", this.onResize);
    window.addEventListener("keyup", this.onKeyup);
  }
  /** 删除window事件 */
  private removeWindowEvent() {
    window.removeEventListener("resize", this.onResize);
    window.removeEventListener("keyup", this.onKeyup);
  }
  /** 键盘事件 */
  private onKeyMap(e) {
    const keyMap = this.config.keyMap;
    let keys: string[] = [];
    for (const key in keyMap) {
      let codes = keyMap[key];
      if (
        (codes === e.keyCode ||
          (Array.isArray(codes) && codes.includes(e.keyCode))) &&
        this[key]
      ) {
        keys.push(key);
      }
    }
    keys = keys.filter((key) => this[`isShow${firstUpCase(key)}`]());
    for (const key of keys) {
      this[key]();
    }
  }
  /**
   * 更新步骤
   * @param id step.id
   * @param step
   */
  updateStep(id: Step["id"], step: Step) {
    const oldStepIndex = this.config.steps.findIndex((v) => v.id === id);
    if (oldStepIndex > -1) {
      const oldStep = this.config.steps[oldStepIndex];
      this.config.steps[oldStepIndex] = { ...oldStep, ...step };
    }
  }
  /**
   * 更新config, 目前仅支持更新主题
   */
  updateConfig({ theme }: { theme: Config["theme"] }) {
    this.renderTheme(theme);
    this.config.theme = theme;
  }
}

/**
 * 创建指引
 * @param config 指引配置
 * @returns guide 对象
 */
export function createGuide(config: Config): Guide {
  return new Guide(config);
}
